use crate::*;

use quote::{format_ident, quote, ToTokens};
use syn::token::{Colon, Comma, Paren};

impl From<ReturnType> for RetType {
    fn from(value: ReturnType) -> Self {
        match value {
            ReturnType::Default => Self::Empty,
            ReturnType::Type(_, t) => Self::Type(t),
        }
    }
}

impl ToTokens for RetType {
    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
        match self {
            Self::Empty => {
                Paren::default().surround(tokens, |_| {});
            }
            Self::Type(t) => {
                t.to_tokens(tokens);
            }
        }
    }
}

enum TypedIdentListToTokensOption {
    OnlyIdent,
    OnlyType,
    Both,
}
struct ToTokensTypedIdentSlice<'a>(&'a [TypedIdent], TypedIdentListToTokensOption);

impl<'a> ToTokens for ToTokensTypedIdentSlice<'a> {
    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
        if self.0.is_empty() {
            return;
        }

        let (rest, tail) = (&self.0[..self.0.len() - 1], self.0.last().unwrap());
        match self.1 {
            TypedIdentListToTokensOption::OnlyIdent => {
                for ti in rest {
                    ti.0.to_tokens(tokens);
                    Comma::default().to_tokens(tokens);
                }
                tail.0.to_tokens(tokens);
            }
            TypedIdentListToTokensOption::OnlyType => {
                for ti in rest {
                    ti.1.to_tokens(tokens);
                    Comma::default().to_tokens(tokens);
                }
                tail.1.to_tokens(tokens);
            }
            TypedIdentListToTokensOption::Both => {
                for ti in rest {
                    ti.0.to_tokens(tokens);
                    Colon::default().to_tokens(tokens);
                    ti.1.to_tokens(tokens);
                    Comma::default().to_tokens(tokens);
                }
                tail.0.to_tokens(tokens);
                Colon::default().to_tokens(tokens);
                tail.1.to_tokens(tokens);
            }
        }
    }
}

impl ToTokens for TypedIdent {
    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
        self.0.to_tokens(tokens);
        Colon::default().to_tokens(tokens);
        self.1.to_tokens(tokens);
    }
}

pub(crate) fn generate(
    option: ServiceMacroOption,
    RpcServiceTrait { vis, name, fns }: RpcServiceTrait,
) -> TokenStream {
    let name_req = format_ident!("{name}Req");
    let name_resp = format_ident!("{name}Resp");

    let calls = &*(1..=fns.len())
        .map(|i| format_ident!("Call{i}"))
        .collect::<Vec<_>>();
    let args = &*fns
        .iter()
        .map(|proc| ToTokensTypedIdentSlice(&proc.args, TypedIdentListToTokensOption::Both))
        .collect::<Vec<_>>();
    let args_names = &*fns
        .iter()
        .map(|proc| ToTokensTypedIdentSlice(&proc.args, TypedIdentListToTokensOption::OnlyIdent))
        .collect::<Vec<_>>();
    let args_types = &*fns
        .iter()
        .map(|proc| ToTokensTypedIdentSlice(&proc.args, TypedIdentListToTokensOption::OnlyType))
        .collect::<Vec<_>>();
    let ret_types = &*fns.iter().map(|proc| &proc.ret_type).collect::<Vec<_>>();
    let fn_names = &*fns.iter().map(|proc| &proc.fn_name).collect::<Vec<_>>();

    let req_static_ref_deep_down_solve = if option.static_ref_deep_down.0 {
        Some(quote!(#[serde(bound(deserialize = "'de: 'static"))]))
    } else {
        None
    };
    let resp_static_ref_deep_down_solve = if option.static_ref_deep_down.1 {
        Some(quote!(#[serde(bound(deserialize = "'de: 'static"))]))
    } else {
        None
    };

    let mut out = quote! {
        use ::tweeny_rpc::utils::{
            maybe_deser_owned::{
                derive_macro::VariantMaybeDeserOwned, MaybeDeserOwned, VariantMaybeDeserOwned,
            },
            serde::{Deserialize, Serialize},
        };
        #[derive(Debug, Serialize, Deserialize, VariantMaybeDeserOwned)]
        #req_static_ref_deep_down_solve
        #vis enum #name_req {
            #(#calls(#args_types)),*
        }
        #[derive(Debug, Serialize, Deserialize, VariantMaybeDeserOwned)]
        #resp_static_ref_deep_down_solve
        #vis enum #name_resp {
            #(#calls(#ret_types)),*
        }
    };

    if option.server_side {
        let name_server_wrapper = format_ident!("{name}ServerWrapper");
        let server = quote! {
            use ::std::future::Future;
            use ::tweeny_rpc::server::ServeRpc;
            #vis trait #name {
                type OtherEndInfo: Send;
                #(fn #fn_names(&self, from: Self::OtherEndInfo, #args) -> impl Future<Output = #ret_types> + Send;)*
            }
            #vis struct #name_server_wrapper<T>(#vis T);
            impl<T> ServeRpc for #name_server_wrapper<T>
            where
                T: #name + Sync + Send + 'static,
            {
                type OtherEndInfo = T::OtherEndInfo;
                type Req = #name_req;
                type Resp = #name_resp;

                fn dispatch_call(
                    &self,
                    req: Self::Req,
                    from: Self::OtherEndInfo,
                ) -> impl Future<Output = Self::Resp> + Send {
                    async move {
                        match req {
                            #(#name_req::#calls(#args_names) => #name_resp::#calls(self.0.#fn_names(from, #args_names).await),)*
                        }
                    }
                }
            }
        };
        out.extend(server);
    }
    if option.client_side {
        let name_req_handle = format_ident!("{name}ReqHandle");
        let client = quote! {
            use ::std::time::Duration;
            use ::tweeny_rpc::{
                client::{error::CallError, RequestHandle},
                courier::codec::{FromSliceDeserializer, ToWriterSerializer},
                utils::OwnedOrRefWithSrc,
            };
            #[derive(Clone)]
            #vis struct #name_req_handle<Ser, Deser>(#vis RequestHandle<#name_req, #name_resp, Ser, Deser>)
            where
                Ser: ToWriterSerializer,
                Deser: FromSliceDeserializer;
            impl<Ser, Deser> #name_req_handle<Ser, Deser>
            where
                Ser: ToWriterSerializer,
                Deser: FromSliceDeserializer,
            {
                #(
                #vis async fn #fn_names(&self, #args, timeout: Option<Duration>)
                    -> Result<OwnedOrRefWithSrc<#ret_types>, CallError<Ser::Err, Deser::Err>>
                {
                    let res = self.0.call(#name_req::#calls(#args_names), timeout).await?;
                    unsafe {
                        res.try_map_inner(|x| match x {
                            #name_resp::#calls(resp) => Ok(resp),
                            _ => Err(CallError::RespMismatch),
                        })
                    }
                }
                )*
            }
        };
        out.extend(client);
    }

    out.into()
}
